<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <p v-text="msg"></p>
    <p v-text="person.avo"></p>
    <p>{{msg}} - {{hello}}</p>
    <ul>
      <li>1.haha</li>
      <li>2.csdks;</li>
      <li>3.dlsjalfoeo</li>
    </ul>
    <input type="text" v-model="msg">
    <p v-model="person.avo"></p>
    <div v-model="hello"></div>
    <div v-html="chtml"></div>
    <div>welcome to vue resource</div>
    <button v-on:click="clickEvent">(v-on)点击一下事件就会生效</button><br/>
    <button @click="clickEvent">(@)点击一下和上面按钮事件一样</button>
    <br/><br/><br/>
    <p style="color: coral;">
      Vue是采用数据劫持配合发布者-订阅者模式的方式，通过<code>Object.defineProperty()</code>来劫持各个属性的<code>setter</code>和<code>getter</code>,
      在数据变动时，发布消息给依赖收集器去通知观察者，做出对应的回调函数去更新视图。<br/><br/>

      <code>MVVM</code>作为绑定的入口，整合<code>Observer, Compile和Watcher</code>三者，通过<code>Observer</code>来监听model数据变化，通过<code>Compile</code>
      来解析编译模板指令，最终利用<code>Watcher</code>搭起<code>Observer, Compile</code>之间的桥梁，达到数据变化驱动视图，视图变化驱动数据变化的双向绑定效果。
    </p>
  </div>
</body>
<script>
  class MVue{
    constructor(options){
      this.$el = options.el;
      this.$data = options.data;
      this.$options = options;

      if(this.$el){
        // 1.实现数据观察者（Observer）
        new Observer(this.$data);

        // 2.实现一个指令解析器（compile）
        new Compile(this.$el, this);
        this.proxyData(this.$data);
      }
    }
    proxyData(data){
      for(const key in data){
        Object.defineProperty(this, key, {
          get(){
            return data[key];
          },
          set(newVal){
            data[key] = newVal;
          }
        });
      }
    }
  }

  const compileUtil = {
    getVal(expr, vm){
      // [person, avo]
      return expr.split(".").reduce((data, currentVal) => {
        return data[currentVal];
      }, vm.$data);
    },
    setVal(expr, vm, inputVal){
      return expr.split(".").reduce((data, currentVal) => {
        data[currentVal] = inputVal;
      }, vm.$data);
    },
    getContentVal(expr, vm){
      return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
        return this.getVal(args[1], vm);
      });
    },
    text(node, expr, vm){ // expr: msg   person.avo {{person}}
      let value;
      if(expr.indexOf("{{") !== -1){
        // {{msg}} - {{hello}}
        value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
          new Watcher(vm, args[1], (newVal)=>{
            this.updater.textUpdater(node, this.getContentVal(expr, vm));
          });
          return this.getVal(args[1], vm);
        });
      }else{
        new Watcher(vm, expr, (newVal)=>{
          this.updater.textUpdater(node, newVal);
        });
        value = this.getVal(expr, vm);
      }
      this.updater.textUpdater(node, value);
    },
    html(node, expr, vm){
      const value = this.getVal(expr, vm);
      new Watcher(vm, expr, (newVal)=>{
        this.updater.htmlUpdater(node, newVal);
      });
      this.updater.htmlUpdater(node, value);
    },
    model(node, expr, vm){
      const value = this.getVal(expr, vm);
      // 数据驱动视图
      new Watcher(vm, expr, (newVal)=>{
        this.updater.modelUpdater(node, newVal);
      });

      // 视图驱动数据
      node.addEventListener("input", (e)=>{
        // 设置值
        this.setVal(expr, vm, e.target.value);
      });

      this.updater.modelUpdater(node, value);
    },
    on(node, expr, vm, eventName){
      let fn = vm.$options.methods && vm.$options.methods[expr];
      node.addEventListener(eventName, fn.bind(vm), false);
    },
    updater: {
      modelUpdater(node, value){
        node.value = value;
      },
      htmlUpdater(node, value){
        node.innerHTML = value;
      },
      textUpdater(node, value){
        node.textContent = value;
      }
    }
  }

  class Compile{
    constructor(el, vm){
      this.el = this.isElementNode(el) ? el : document.querySelector(el);
      this.vm = vm;
      // 1. 获取文档碎片对象，放入内存中会减少页面的回流和重绘
      const fragment = this.node2Fragment(this.el);

      // 2. 编译模板
      this.compile(fragment);

      // 3.追加子元素到根元素上去
      this.el.appendChild(fragment);
    }

    compile(fragment){
      // 1.获取元素
      const childNodes = fragment.childNodes;
      [...childNodes].forEach(child => {
        if(this.isElementNode(child)){
          // 是元素节点, 编译元素节点
          this.compileElement(child);
        } else {
          // 是文本节点，编译文本节点
          this.compileText(child);
        }
        if(child.childNodes && child.childNodes.length){
          // 递归遍历全部子节点
          this.compile(child);
        }
      });
    }
    compileElement(node){
      const attributes = node.attributes;
      [...attributes].forEach(attr => {
        const {name, value} = attr;
        if(this.isDirective(name)){
          const [,directive] = name.split("-");
          const [dirName, eventName] = directive.split(":");
          // 更新数据， 数据驱动视图
          compileUtil[dirName](node, value, this.vm, eventName);

          // 删除指令上的属性
          node.removeAttribute("v-"+directive);
        } else if (this.isEventName(name)){
          let [,eventName] = name.split("@");
          compileUtil["on"](node, value, this.vm, eventName);
        }
      });
    }
    compileText(node){
      const content = node.textContent;
      if(/\{\{.+?\}\}/.test(content)){
        compileUtil["text"](node, content, this.vm);
      }
    }
    node2Fragment(el){
      const f = document.createDocumentFragment();
      let firstChild;
      while(firstChild = el.firstChild){
        f.appendChild(firstChild);
      }
      return f;
    }
    isDirective(attrName){
      return /^v-/.test(attrName);
    }
    isElementNode(node){
      return node.nodeType === 1;
    }
    isEventName(attrName){
      return /^@/.test(attrName);
    }
  }

  class Observer{
    constructor(data){
      this.observe(data);
    }
    observe(data){
      if(data && typeof data === "object"){
        Object.keys(data).forEach(key=>{
          this.defineReactive(data, key, data[key]);
        });
      }
    }
    defineReactive(obj, key, value){
      // 递归遍历
      this.observe(value);
      let dep = new Dep();
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: false,
        get(){
          // 数据变化时，往Dep中添加观察者。（添加依赖）
          Dep.target && dep.addSub(Dep.target);
          return value;
        },
        set: (newVal) => {
          this.observe(newVal);
          if(newVal !== value){
            value = newVal;
          }
          dep.notify();
        }
      });
    }
  }

  // 观察者,也就是判断新值与旧值
  class Watcher{
    constructor(vm, expr, cb){
      this.expr = expr;
      this.vm = vm;
      this.cb = cb;
      // 先把旧值保存起来
      this.oldVal = this.getOldVal();
    }
    getOldVal(){
      Dep.target = this;
      const oldVal = compileUtil.getVal(this.expr, this.vm);
      Dep.target = null;
      return oldVal;
    }
    update(){
      const newVal = compileUtil.getVal(this.expr, this.vm);
      if(newVal !== this.oldVal){
        this.cb(newVal);
      }
    }
  }

  // 收集依赖
  class Dep{
    constructor(){
      this.subs = [];
    }
    // 收集观察者
    addSub(watcher){
      this.subs.push(watcher);
    }
    // 通知观察者去更新
    notify(){
      console.log("通知了更新");
      this.subs.forEach(w=>w.update());
    }
  }

  var app = new MVue({
    el: "#app",
    data: {
      msg: "hello",
      hello: "Kaifa",
      person: {
        avo: "haha"
      },
      chtml: "<h1>这里就是html标签内容</h1>"
    },
    methods: {
      clickEvent() {
        alert("你点击了按钮，并且事件已经生效了");
      }
    }
  })
</script>
</html>